package org.therandoms.textgen;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * This loads the contents of an XML file into a {@link TextGeneratorDictionary}
 * . The file format is as follows:
 * 
 * <pre>
 * 	&lt;dictionary&gt;
 * 		&lt;production_category name=""&gt;
 * 			&lt;production delim=""&gt;
 * 				&lt;symbol&gt;
 * 					SymbolGoesHere
 * 				&lt;/symbol&gt;
 * 			&lt;/production&gt;
 * 		&lt;/production_category&gt;
 * 	&lt;/dictionary&gt;
 * </pre>
 * 
 * You can have as many production_category elements and as many production and
 * symbol elements as you want.
 * 
 * @author Logan Widick
  * @author Vinh Pham
 */
public class TextGeneratorDictionaryXMLWorker<T> {
	/**
	 * The tag name for production categories
	 */
	public static final String PRODUCTION_CATEGORY_TAG_NAME = "production_category";

	/**
	 * The attribute that contains the name of the production category
	 */
	public static final String PRODUCTION_CATEGORY_ATTRIBUTE_NAME = "name";

	/**
	 * The tag name for a production
	 */
	public static final String PRODUCTION_TAG_NAME = "production";

	/**
	 * The attribute that has the production delimiter
	 */
	public static final String PRODUCTION_DELIMITER_ATTRIBUTE_NAME = "delim";

	/**
	 * The tag name for a symbol within a production
	 */
	public static final String SYMBOL_TAG_NAME = "symbol";

	/**
	 * The root element tag name
	 */
	private static final String ROOT_ELEMENT_TAG_NAME = "dictionary";

	/**
	 * Generates a {@link TextGeneratorDictionary} from a XML document.
	 * 
	 * @param mDoc
	 *            the document
	 * @return the {@link TextGeneratorDictionary} or null.
	 */
	public static TextGeneratorDictionary<String> createFromXMLDocument(
			Document mDoc) {
		if (mDoc == null) {
			return null;
		}

		// for some stupid reason NodeList isn't Iterable.
		NodeList productionCategoryNodeList = mDoc.getDocumentElement()
				.getElementsByTagName(PRODUCTION_CATEGORY_TAG_NAME);
		if (productionCategoryNodeList == null) {
			return null;
		}

		TextGeneratorDictionary<String> retDict = new TextGeneratorDictionary<String>();
		for (int i = 0; i < productionCategoryNodeList.getLength(); i++) {

			// for each node representing a production category
			Node mNode = productionCategoryNodeList.item(i);

			// get attributes, then find attribute for the name, then get value
			NamedNodeMap attributesList = mNode.getAttributes();
			if (attributesList == null) {
				continue;
			}
			Node attribNode = attributesList
					.getNamedItem(PRODUCTION_CATEGORY_ATTRIBUTE_NAME);
			if (attribNode == null) {
				continue;
			}
			String categoryName = attribNode.getNodeValue();
			if (categoryName == null) {
				continue;
			}
			categoryName = categoryName.trim();
			if (categoryName.isEmpty()) {
				continue;
			}

			// add production category
			retDict.addProductionCategory(categoryName);

			// for each child node
			NodeList productionNodeList = mNode.getChildNodes();
			for (int j = 0; j < productionNodeList.getLength(); j++) {
				Production<String> production = new Production<String>();

				Node currNode = productionNodeList.item(j);

				// get delimiter
				NamedNodeMap productionAttrsMap = currNode.getAttributes();
				if (productionAttrsMap != null) {
					Node productionDelimNode = productionAttrsMap
							.getNamedItem(PRODUCTION_DELIMITER_ATTRIBUTE_NAME);
					if (productionDelimNode != null) {
						production.setDelimiter(productionDelimNode
								.getNodeValue());
					}
				}

				// add symbols to production
				NodeList symbolNodeList = currNode.getChildNodes();
				for (int k = 0; k < symbolNodeList.getLength(); k++) {
					String currSym = symbolNodeList.item(k).getTextContent();
					if (currSym == null) {
						continue;
					}
					currSym = currSym.trim();
					if (currSym.isEmpty()) {
						continue;
					}
					production.addSymbolToProduction(currSym);
				}

				// add the production
				if (!production.isEmpty()) {
					retDict.addProductionToCategory(production, categoryName);
				}
			}
		}

		return retDict;

	}

	/**
	 * Generates an XML document from the text generator dictionary.
	 * 
	 * @param dict
	 *            the {@link TextGeneratorDictionary}
	 * @return the document
	 */
	public static Document saveToXMLDocument(
			TextGeneratorDictionary<String> dict) {
		if (dict == null) {
			return null;
		}
		Document retDoc = null;
		Element rootElem = null;

		try {
			DocumentBuilderFactory docFactory = DocumentBuilderFactory
					.newInstance();
			DocumentBuilder docBuilder = docFactory.newDocumentBuilder();

			retDoc = docBuilder.newDocument();

			rootElem = retDoc.createElement(ROOT_ELEMENT_TAG_NAME);
			retDoc.appendChild(rootElem);
		} catch (ParserConfigurationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return null;
		}

		// for each production category
		for (String productionCategory : dict.getProductionCategories()) {
			try {
				// make and append child <production_category> element
				Element prodCatElem = retDoc
						.createElement(PRODUCTION_CATEGORY_TAG_NAME);
				rootElem.appendChild(prodCatElem);
				prodCatElem.setAttribute(PRODUCTION_CATEGORY_ATTRIBUTE_NAME,
						productionCategory);

				// for each production
				for (Production<String> production : dict
						.getProductionsForCategory(productionCategory)) {
					try {
						// make and append child <production> element
						Element prodElem = retDoc
								.createElement(PRODUCTION_TAG_NAME);
						prodElem.setAttribute(
								PRODUCTION_DELIMITER_ATTRIBUTE_NAME,
								production.getDelimiter());
						prodCatElem.appendChild(prodElem);
						for (String symbol : production.getSymbols()) {
							try {
								Element symElem = retDoc
										.createElement(SYMBOL_TAG_NAME);
								symElem.setTextContent(symbol);
								prodElem.appendChild(symElem);
							} catch (Exception exc) {
								exc.printStackTrace();
							}
						}
					} catch (Exception ex) {
						ex.printStackTrace();
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		return retDoc;
	}
}
